import { Injectable, BadRequestException, InternalServerErrorException, Logger } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import sharp from 'sharp'
import * as path from 'path'
import * as fs from 'fs/promises'
// import { v4 as uuidv4 } from 'uuid';
import { DatabaseService } from '../database/database.service'
import { FileType, ImageProcessDto } from './dto/upload.dto'

/**
 * 文件上传服务
 * 处理文件上传、图片处理、文件存储等功能
 */
@Injectable()
export class UploadService {
  private readonly logger = new Logger(UploadService.name)
  private readonly uploadPath: string
  private readonly maxFileSize: number
  private readonly allowedImageTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif']
  private readonly allowedDocumentTypes = [
    'application/pdf',
    'application/msword',
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    'application/vnd.ms-excel',
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
  ]

  constructor(
    private readonly configService: ConfigService,
    private readonly databaseService: DatabaseService
  ) {
    this.uploadPath = this.configService.get<string>('UPLOAD_PATH', './uploads')
    this.maxFileSize = this.configService.get<number>('MAX_FILE_SIZE', 10 * 1024 * 1024) // 10MB
    this.ensureUploadDirectory()
  }

  /**
   * 确保上传目录存在
   */
  private async ensureUploadDirectory() {
    try {
      await fs.access(this.uploadPath)
    } catch {
      await fs.mkdir(this.uploadPath, { recursive: true })
      this.logger.log(`创建上传目录: ${this.uploadPath}`)
    }
  }

  /**
   * 上传单个文件
   * @param file 上传的文件
   * @param userId 用户ID
   * @param type 文件类型
   * @param description 文件描述
   * @param tags 文件标签
   * @returns 文件信息
   */
  async uploadFile(file: Express.Multer.File, userId: string, type?: FileType, description?: string, tags?: string) {
    try {
      // 验证文件
      this.validateFile(file)

      // 确定文件类型
      const fileType = type || this.determineFileType(file.mimetype)

      // 生成文件名和路径
      const fileName = this.generateFileName(file.originalname)
      const relativePath = this.getRelativePath(fileType, fileName)
      const fullPath = path.join(this.uploadPath, relativePath)

      // 确保目录存在
      await fs.mkdir(path.dirname(fullPath), { recursive: true })

      // 保存文件
      await fs.writeFile(fullPath, file.buffer)

      // 保存文件信息到数据库
      const fileRecord = await this.databaseService.file.create({
        data: {
          originalName: file.originalname,
          filename: fileName,
          path: relativePath,
          mimeType: file.mimetype,
          size: file.size,
          metadata: {
            type: fileType,
            description,
            tags
          },
          uploadedBy: userId
        }
      })

      this.logger.log(`文件上传成功: ${file.originalname} -> ${fileName}`)

      return {
        id: fileRecord.id,
        originalName: fileRecord.originalName,
        filename: fileRecord.filename,
        filePath: fileRecord.path,
        url: this.getFileUrl(fileRecord.path),
        mimeType: fileRecord.mimeType,
        size: fileRecord.size,
        type: (fileRecord.metadata as any)?.type || 'other',
        description: (fileRecord.metadata as any)?.description || '',
        tags: (fileRecord.metadata as any)?.tags || '',
        createdAt: fileRecord.createdAt
      }
    } catch (error) {
      this.logger.error(`文件上传失败: ${error.message}`, error.stack)
      throw error
    }
  }

  /**
   * 上传并处理图片
   * @param file 图片文件
   * @param userId 用户ID
   * @param processOptions 处理选项
   * @param description 文件描述
   * @param tags 文件标签
   * @returns 处理后的图片信息
   */
  async uploadImage(
    file: Express.Multer.File,
    userId: string,
    processOptions?: ImageProcessDto,
    description?: string,
    tags?: string
  ) {
    try {
      // 验证图片文件
      if (!this.allowedImageTypes.includes(file.mimetype)) {
        throw new BadRequestException('不支持的图片格式')
      }

      // 处理图片
      const processedBuffer = await this.processImage(file.buffer, processOptions)

      // 创建处理后的文件对象
      const processedFile: Express.Multer.File = {
        ...file,
        buffer: processedBuffer,
        size: processedBuffer.length,
        mimetype: this.getMimeTypeFromFormat(processOptions?.format || 'jpeg')
      }

      // 上传处理后的文件
      return await this.uploadFile(processedFile, userId, FileType.IMAGE, description, tags)
    } catch (error) {
      this.logger.error(`图片上传失败: ${error.message}`, error.stack)
      throw error
    }
  }

  /**
   * 处理图片
   * @param buffer 图片缓冲区
   * @param options 处理选项
   * @returns 处理后的图片缓冲区
   */
  private async processImage(buffer: Buffer, options?: ImageProcessDto): Promise<Buffer> {
    try {
      let image = sharp(buffer)

      // 调整尺寸
      if (options?.width || options?.height) {
        image = image.resize(options.width, options.height, {
          fit: 'inside',
          withoutEnlargement: true
        })
      }

      // 设置格式和质量
      const format = options?.format || 'jpeg'
      const quality = options?.quality || 80

      switch (format) {
        case 'jpeg':
          image = image.jpeg({ quality })
          break
        case 'png':
          image = image.png({ quality })
          break
        case 'webp':
          image = image.webp({ quality })
          break
        case 'avif':
          image = image.avif({ quality })
          break
      }

      return await image.toBuffer()
    } catch (error) {
      this.logger.error(`图片处理失败: ${error.message}`, error.stack)
      throw new InternalServerErrorException('图片处理失败')
    }
  }

  /**
   * 删除文件
   * @param fileId 文件ID
   * @param userId 用户ID
   */
  async deleteFile(fileId: string, userId: string): Promise<void> {
    try {
      // 查找文件记录
      const file = await this.databaseService.file.findUnique({
        where: { id: fileId }
      })

      if (!file) {
        throw new BadRequestException('文件不存在')
      }

      // 检查权限（只有上传者可以删除）
      if (file.uploadedBy !== userId) {
        throw new BadRequestException('无权限删除此文件')
      }

      // 删除物理文件
      const fullPath = path.join(this.uploadPath, file.path)
      try {
        await fs.unlink(fullPath)
      } catch (error) {
        this.logger.warn(`删除物理文件失败: ${fullPath}`, error.message)
      }

      // 删除数据库记录
      await this.databaseService.file.delete({
        where: { id: fileId }
      })

      this.logger.log(`文件删除成功: ${file.filename}`)
    } catch (error) {
      this.logger.error(`文件删除失败: ${error.message}`, error.stack)
      throw error
    }
  }

  /**
   * 获取文件信息
   * @param fileId 文件ID
   * @returns 文件信息
   */
  async getFileInfo(fileId: string) {
    try {
      const file = await this.databaseService.file.findUnique({
        where: { id: fileId },
        include: {
          uploader: {
            select: {
              id: true,
              username: true,
              email: true
            }
          }
        }
      })

      if (!file) {
        throw new BadRequestException('文件不存在')
      }

      return {
        ...file,
        url: this.getFileUrl(file.path)
      }
    } catch (error) {
      this.logger.error(`获取文件信息失败: ${error.message}`, error.stack)
      throw error
    }
  }

  /**
   * 验证文件
   * @param file 文件对象
   */
  private validateFile(file: Express.Multer.File): void {
    if (!file) {
      throw new BadRequestException('未选择文件')
    }

    if (file.size > this.maxFileSize) {
      throw new BadRequestException(`文件大小超过限制 (${this.maxFileSize / 1024 / 1024}MB)`)
    }

    if (!file.originalname) {
      throw new BadRequestException('文件名无效')
    }
  }

  /**
   * 确定文件类型
   * @param mimeType MIME类型
   * @returns 文件类型
   */
  private determineFileType(mimeType: string): FileType {
    if (this.allowedImageTypes.includes(mimeType)) {
      return FileType.IMAGE
    }
    if (this.allowedDocumentTypes.includes(mimeType)) {
      return FileType.DOCUMENT
    }
    if (mimeType.startsWith('video/')) {
      return FileType.VIDEO
    }
    if (mimeType.startsWith('audio/')) {
      return FileType.AUDIO
    }
    return FileType.OTHER
  }

  /**
   * 生成文件名
   * @param originalName 原始文件名
   * @returns 新文件名
   */
  private generateFileName(originalName: string): string {
    const ext = path.extname(originalName)
    const uuid = Math.random().toString(36).substring(2)
    return `${uuid}${ext}`
  }

  /**
   * 获取相对路径
   * @param type 文件类型
   * @param fileName 文件名
   * @returns 相对路径
   */
  private getRelativePath(type: FileType, fileName: string): string {
    const date = new Date()
    const year = date.getFullYear()
    const month = String(date.getMonth() + 1).padStart(2, '0')
    return path.join(type, year.toString(), month, fileName)
  }

  /**
   * 获取文件URL
   * @param relativePath 相对路径
   * @returns 文件URL
   */
  private getFileUrl(relativePath: string): string {
    const baseUrl = this.configService.get<string>('BASE_URL', 'http://localhost:3000')
    return `${baseUrl}/uploads/${relativePath.replace(/\\/g, '/')}`
  }

  /**
   * 根据格式获取MIME类型
   * @param format 图片格式
   * @returns MIME类型
   */
  private getMimeTypeFromFormat(format: string): string {
    const mimeTypes = {
      jpeg: 'image/jpeg',
      png: 'image/png',
      webp: 'image/webp',
      avif: 'image/avif'
    }
    return mimeTypes[format] || 'image/jpeg'
  }
}
